(function umd(root, factory) {
	if (typeof module === "object" && typeof exports === "object") module.exports = factory();
	else if (typeof define === "function" && define.amd) define([], factory);
	else root.httpVueLoader = factory();
})(this, function factory() {
	"use strict";
	var scopeIndex = 0;
	StyleContext.prototype = {
		withBase: function (callback) {
			var tmpBaseElt;
			if (this.component.baseURI) {
				// firefox and chrome need the <base> to be set while inserting or modifying <style> in a document.
				tmpBaseElt = document.createElement("base");
				tmpBaseElt.href = this.component.baseURI;
				var headElt = this.component.getHead();
				headElt.insertBefore(tmpBaseElt, headElt.firstChild);
			}
			callback.call(this);
			if (tmpBaseElt) this.component.getHead().removeChild(tmpBaseElt);
		},
		scopeStyles: function (styleElt, scopeName) {
			function process() {
				var sheet = styleElt.sheet;
				var rules = sheet.cssRules;
				for (var i = 0; i < rules.length; ++i) {
					var rule = rules[i];
					if (rule.type !== 1) continue;
					var scopedSelectors = [];
					rule.selectorText.split(/\s*,\s*/).forEach(function (sel) {
						scopedSelectors.push(scopeName + " " + sel);
						var segments = sel.match(/([^ :]+)(.+)?/);
						scopedSelectors.push(segments[1] + scopeName + (segments[2] || ""));
					});
					var scopedRule =
						scopedSelectors.join(",") + rule.cssText.substr(rule.selectorText.length);
					sheet.deleteRule(i);
					sheet.insertRule(scopedRule, i);
				}
			}
			try {
				// firefox may fail sheet.cssRules with InvalidAccessError
				process();
			} catch (ex) {
				if (ex instanceof DOMException && ex.code === DOMException.INVALID_ACCESS_ERR) {
					styleElt.sheet.disabled = true;
					styleElt.addEventListener("load", function onStyleLoaded() {
						styleElt.removeEventListener("load", onStyleLoaded);
						// firefox need this timeout otherwise we have to use document.importNode(style, true)
						setTimeout(function () {
							process();
							styleElt.sheet.disabled = false;
						});
					});
					return;
				}
				throw ex;
			}
		},
		compile: function () {
			var hasTemplate = this.template !== null;
			var scoped = this.elt.hasAttribute("scoped");
			if (scoped) {
				// no template, no scopable style needed
				if (!hasTemplate) return;
				// firefox does not tolerate this attribute
				this.elt.removeAttribute("scoped");
			}
			this.withBase(function () {
				this.component.getHead().appendChild(this.elt);
			});
			if (scoped) this.scopeStyles(this.elt, "[" + this.component.getScopeId() + "]");
			return Promise.resolve();
		},
		getContent: function () {
			return this.elt.textContent;
		},
		setContent: function (content) {
			this.withBase(function () {
				this.elt.textContent = content;
			});
		},
	};
	function StyleContext(component, elt) {
		this.component = component;
		this.elt = elt;
	}

	ScriptContext.prototype = {
		getContent: function () {
			return this.elt.textContent.replace(/export default/, "module.exports =");
		},
		setContent: function (content) {
			this.elt.textContent = content;
		},
		compile: function (module) {
			var childModuleRequire = function (childURL) {
				return httpVueLoader.require(resolveURL(this.component.baseURI, childURL));
			}.bind(this);
			var childLoader = function (childURL, childName) {
				return httpVueLoader(resolveURL(this.component.baseURI, childURL), childName);
			}.bind(this);
			try {
				Function("exports", "require", "httpVueLoader", "module", this.getContent()).call(
					this.module.exports,
					this.module.exports,
					childModuleRequire,
					childLoader,
					this.module
				);
			} catch (ex) {
				if (!("lineNumber" in ex)) {
					return Promise.reject(ex);
				}
				var vueFileData = responseText.replace(/\r?\n/g, "\n");
				var lineNumber =
					vueFileData.substr(0, vueFileData.indexOf(script)).split("\n").length + ex.lineNumber - 1;
				throw new ex.constructor(ex.message, url, lineNumber);
			}
			return Promise.resolve(this.module.exports)
				.then(httpVueLoader.scriptExportsHandler.bind(this))
				.then(
					function (exports) {
						this.module.exports = exports;
					}.bind(this)
				);
		},
	};
	function ScriptContext(component, elt) {
		this.component = component;
		this.elt = elt;
		this.module = {exports: {}};
	}

	TemplateContext.prototype = {
		getContent: function () {
			return this.elt.innerHTML;
		},
		setContent: function (content) {
			this.elt.innerHTML = content;
		},
		getRootElt: function () {
			var tplElt = this.elt.content || this.elt;
			if ("firstElementChild" in tplElt) return tplElt.firstElementChild;
			for (tplElt = tplElt.firstChild; tplElt !== null; tplElt = tplElt.nextSibling)
				if (tplElt.nodeType === Node.ELEMENT_NODE) return tplElt;
			return null;
		},
		compile: function () {
			return Promise.resolve();
		},
	};
	function TemplateContext(component, elt) {
		this.component = component;
		this.elt = elt;
	}

	Component.prototype = {
		getHead: function () {
			return document.head || document.getElementsByTagName("head")[0];
		},
		getScopeId: function () {
			if (this._scopeId === "") {
				this._scopeId = "data-s-" + (scopeIndex++).toString(36);
				this.template.getRootElt().setAttribute(this._scopeId, "");
			}
			return this._scopeId;
		},
		load: function (componentURL) {
			return httpVueLoader.httpRequest(componentURL).then(
				function (responseText) {
					this.baseURI = componentURL.substr(0, componentURL.lastIndexOf("/") + 1);
					var doc = document.implementation.createHTMLDocument("");
					// IE requires the <base> to come with <style>
					doc.body.innerHTML =
						(this.baseURI ? '<base href="' + this.baseURI + '">' : "") + responseText;
					for (var it = doc.body.firstChild; it; it = it.nextSibling) {
						switch (it.nodeName) {
							case "TEMPLATE":
								this.template = new TemplateContext(this, it);
								break;
							case "SCRIPT":
								this.script = new ScriptContext(this, it);
								break;
							case "STYLE":
								this.styles.push(new StyleContext(this, it));
								break;
						}
					}
					return this;
				}.bind(this)
			);
		},
		_normalizeSection: function (eltCx) {
			var p;
			if (eltCx === null || !eltCx.elt.hasAttribute("src")) {
				p = Promise.resolve(null);
			} else {
				p = httpVueLoader.httpRequest(eltCx.elt.getAttribute("src")).then(function (content) {
					eltCx.elt.removeAttribute("src");
					return content;
				});
			}
			return p
				.then(
					function (content) {
						if (eltCx !== null && eltCx.elt.hasAttribute("lang")) {
							var lang = eltCx.elt.getAttribute("lang");
							eltCx.elt.removeAttribute("lang");
							return httpVueLoader.langProcessor[lang.toLowerCase()].call(
								this,
								content === null ? eltCx.getContent() : content
							);
						}
						return content;
					}.bind(this)
				)
				.then(function (content) {
					if (content !== null) eltCx.setContent(content);
				});
		},
		normalize: function () {
			return Promise.all(
				Array.prototype.concat(
					this._normalizeSection(this.template),
					this._normalizeSection(this.script),
					this.styles.map(this._normalizeSection)
				)
			).then(
				function () {
					return this;
				}.bind(this)
			);
		},
		compile: function () {
			return Promise.all(
				Array.prototype.concat(
					this.template && this.template.compile(),
					this.script && this.script.compile(),
					this.styles.map(function (style) {
						return style.compile();
					})
				)
			).then(
				function () {
					return this;
				}.bind(this)
			);
		},
	};
	function Component(name) {
		this.name = name;
		this.template = null;
		this.script = null;
		this.styles = [];
		this._scopeId = "";
	}
	function identity(value) {
		return value;
	}
	function parseComponentURL(url) {
		var comp = url.match(/(.*?)([^/]+?)\/?(\.vue)?(\?.*|#.*|$)/);
		return {
			name: comp[2],
			url: comp[1] + comp[2] + (comp[3] === undefined ? "/index.vue" : comp[3]) + comp[4],
		};
	}
	function resolveURL(baseURL, url) {
		if (url.substr(0, 2) === "./" || url.substr(0, 3) === "../") {
			return baseURL + url;
		}
		return url;
	}

	httpVueLoader.load = function (url, name) {
		return function () {
			return new Component(name)
				.load(url)
				.then(function (component) {
					return component.normalize();
				})
				.then(function (component) {
					return component.compile();
				})
				.then(function (component) {
					var exports = component.script !== null ? component.script.module.exports : {};
					if (component.template !== null) exports.template = component.template.getContent();
					if (exports.name === undefined)
						if (component.name !== undefined) exports.name = component.name;
					exports._baseURI = component.baseURI;
					return exports;
				});
		};
	};

	httpVueLoader.register = function (Vue, url) {
		var comp = parseComponentURL(url);
		Vue.component(comp.name, httpVueLoader.load(comp.url));
	};
	httpVueLoader.install = function (Vue) {
		Vue.mixin({
			beforeCreate: function () {
				var components = this.$options.components;
				for (var componentName in components) {
					if (
						typeof components[componentName] === "string" &&
						components[componentName].substr(0, 4) === "url:"
					) {
						var comp = parseComponentURL(components[componentName].substr(4));
						var componentURL =
							"_baseURI" in this.$options ? resolveURL(this.$options._baseURI, comp.url) : comp.url;
						if (isNaN(componentName))
							components[componentName] = httpVueLoader.load(componentURL, componentName);
						else
							components[componentName] = Vue.component(
								comp.name,
								httpVueLoader.load(componentURL, comp.name)
							);
					}
				}
			},
		});
	};
	httpVueLoader.require = function (moduleName) {
		return window[moduleName];
	};
	httpVueLoader.httpRequest = function (url) {
		return new Promise(function (resolve, reject) {
			var xhr = new XMLHttpRequest();
			xhr.open("GET", url);
			xhr.responseType = "text";
			xhr.onreadystatechange = function () {
				if (xhr.readyState === 4) {
					if (xhr.status >= 200 && xhr.status < 300) resolve(xhr.responseText);
					else reject(xhr.status);
				}
			};
			xhr.send(null);
		});
	};
	httpVueLoader.langProcessor = {
		html: identity,
		js: identity,
		css: identity,
	};
	httpVueLoader.scriptExportsHandler = identity;
	function httpVueLoader(url, name) {
		var comp = parseComponentURL(url);
		return httpVueLoader.load(comp.url, name);
	}
	return httpVueLoader;
});
